#include <cassert>
#include <functional>
#include <iostream>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <sstream>

#include <stdio.h>
#include <stdlib.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>

#include <pybind11/embed.h>
#include <pybind11/functional.h>
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>

class MessagePipe {
public:
  MessagePipe() {
    // initialize
    Init();
  }

  ~MessagePipe() {
    // close all fd
    Close();
  }

  bool is_ready() const {
    // ready
    return ready_;
  }

  int python_read_fd() const {
    // python read fd
    return cpp_to_python_[0];
  }

  void Init() {
    int success = 0;

    success = pipe(cpp_to_python_);
    if (success < 0) {
      perror("Failed to created cpp_to_python");
      return;
    }

    success = pipe(python_to_cpp_);
    if (success < 0) {
      perror("Failed to created python_to_cpp");
      return;
    }

    ready_ = true;
  }

  void Close() {
    close(cpp_to_python_[0]);
    close(cpp_to_python_[1]);
    close(python_to_cpp_[0]);
    close(python_to_cpp_[1]);
    ready_ = false;
  }

  void WriteMessageToPython(const std::string &msg) {
    auto size = static_cast<int64_t>(msg.size());
    write(cpp_to_python_[1], &size, sizeof(size));
    write(cpp_to_python_[1], msg.c_str(), msg.size());
    std::cout << "size: " << size << ", " << msg;
  }

  void WriteMessageToCpp(const std::string &msg) {
    auto size = static_cast<int64_t>(msg.size());
    write(python_to_cpp_[1], &size, sizeof(size));
    write(python_to_cpp_[1], msg.c_str(), msg.size());
  }

  std::string ReadMessageToCpp() {
    read(python_to_cpp_[0], &cpp_buf_size_, sizeof(cpp_buf_size_));
    read(python_to_cpp_[0], cpp_buf_, cpp_buf_size_);
    return std::string(cpp_buf_, cpp_buf_size_);
  }

  std::string ReadMessageToPython() {
    read(cpp_to_python_[0], &py_buf_size_, sizeof(py_buf_size_));
    read(cpp_to_python_[0], py_buf_, py_buf_size_ + 10);
    return std::string(py_buf_, py_buf_size_);
  }

private:
  int cpp_to_python_[2];
  int python_to_cpp_[2];
  bool ready_{false};

  char cpp_buf_[4096 * 16];
  int64_t cpp_buf_size_{0};

  char py_buf_[4096 * 16];
  int64_t py_buf_size_{0};
};

// Make it thread-safe
MessagePipe* GetMessagePipe() {
  static auto queue = std::make_unique<MessagePipe>();
  return queue.get();
}

PYBIND11_EMBEDDED_MODULE(message_pipe, m) {
  m.def("get_python_read_fd", []() { return GetMessagePipe()->python_read_fd(); });
  m.def("write_message", [](const std::string& msg) { GetMessagePipe()->WriteMessageToCpp(msg); });
};

void start_python() {
  pybind11::scoped_interpreter guard{};
  pybind11::module_::import("pymod_pipe");
}

void start_cpp() {
  for (int n = 0; n < 10; n++) {
    std::cout << "send msg in cpp! " << n << std::endl;
    GetMessagePipe()->WriteMessageToPython(
        "hello world, this is from cpp: " + std::to_string(n));
  }
}

int main(int argc, char *argv[]) {
  GetMessagePipe();
  std::thread python_thread(start_python);

  start_cpp();
  python_thread.join();
  return 0;
}
